/************************************************************************************
**  
* @copyright (c) 2013-2100,  Technology Co., LTD. All Right Reserved.
*
*************************************************************************************/
/**
* @file     duye_smart_ptr.h
* @version     
* @brief      
* @author
* @date     2013-02-28
* @note 
*
*  1. 2013-02-28 Created this file
* 
*/
#pragma once

#include <duye_type.h>
#include <duye_lock.h>

namespace duye {

/**
 * @brief smart ptr
 * @example:
 * class MyClass {};
 * SmartPtr<MyClass> myClassObj(new MyClass());
 */
template <typename T>
class SmartPtr {
public:
    SmartPtr() : m_object(NULL), m_counter(NULL), m_mutex(NULL) {}
    explicit SmartPtr(T* object, bool threadSafe = true)
        : m_object(object)
        , m_counter(object ? new uint32(1) : NULL)
        , m_mutex(threadSafe ? new Mutex() : NULL) {}
    
    SmartPtr(const SmartPtr<T>& smartPtr)   
        : m_object(smartPtr.m_object)
        , m_counter(smartPtr.m_counter)
        , m_mutex(smartPtr.m_mutex) {
        if (m_mutex) m_mutex->lock();  
        if (m_counter) ++(*m_counter);    
        if (m_mutex) m_mutex->unlock();
    }
    
    SmartPtr(T* object, unsigned int* counter, Mutex* mutex)
        : m_object(object)
        , m_counter(counter)
        , m_mutex(mutex) {            
        if (m_mutex) m_mutex->lock();
        if (m_counter) ++(*m_counter);
        if (m_mutex) m_mutex->unlock();
    }
    
    ~SmartPtr() { release(); }

    SmartPtr<T>& operator=(const SmartPtr<T>& smartPtr) {
        if (this != &smartPtr) {
            release(); 
            m_object = smartPtr.m_object;
            m_counter = smartPtr.m_counter;
            m_mutex = smartPtr.m_mutex;

            if (m_mutex) m_mutex->lock();
            if (m_counter) ++(*m_counter);
            if (m_mutex) m_mutex->unlock();
        }

        return *this;
    }
    
    SmartPtr<T>& operator=(T* object) {
        release();
        m_object = object; 
        m_counter = object ? new unsigned int(1) : NULL;
        m_mutex = NULL;
        return *this;
    }

    T& operator*() const {return *m_object;}
    T* operator->() const {return m_object;}

    bool operator==(const SmartPtr<T>& smartPtr) const {return m_object == smartPtr.m_object;}
    bool operator!=(const SmartPtr<T>& smartPtr) const {return m_object != smartPtr.m_object;}

    template <typename U> operator SmartPtr<U>() {
        return SmartPtr<U>(m_object, m_counter, m_mutex);
    }

    // methods    
    /**     
    * Returns the naked pointer value.     
    */
    T* as() const { return m_object; }

    /**     
    * Returns the reference counter value.   
    */    
    SmartPtr getCounter() const { return *m_counter; }

    /**
    * Returns whether this references a NULL object.     
    */   
    bool isNull() const { return m_object == NULL; }

    /**     
    * Detach the reference from the shared object.     
    * The reference count is decremented, but the object is not deleted if the     
    * reference count becomes 0.     
    * After the method returns, this reference does not point to any shared object.     
    */
    void detach() { release(true); }

private:
    void release(bool detachOnly = false) {    
        bool last_reference = false;
        if (m_mutex) {
            m_mutex->lock();
        }

        if (m_counter && --(*m_counter) == 0) {
            delete m_counter;

            if (!detachOnly) {
                delete m_object;
            }

            last_reference = true;
        }
        
        m_counter = NULL; 
        m_object  = NULL;
        
        if (m_mutex) {
            Mutex* mutex = m_mutex;            
            // reset m_mutex prior to releasing the lock
            // in case another reference is waiting on the mutex
            // so that it doesn't try to unlock it while we're deleting it here afterwards
            if (last_reference) { 
                m_mutex = NULL;
            }
            
            mutex->unlock();

            if (last_reference) {
                delete mutex;
            }
        }
    }

private:
    T* m_object;    
    uint32* m_counter;
    Mutex* m_mutex;
};

}
